package com.fll.server;

import com.fll.common.Config;
import com.fll.common.container.Container;
import com.fll.common.container.ContainerHelper;
import com.fll.protocol.IdleCheckHandler;
import com.fll.protocol.ProxyMessageDecoder;
import com.fll.protocol.ProxyMessageEncoder;
import com.fll.server.config.ProxyConfig;
import com.fll.server.handlers.ServerChannelHandler;
import com.fll.server.handlers.UserChannelHandler;
import com.fll.server.metrics.handler.BytesMetricsHandler;
import com.fll.server.web.WebConfigContainer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.ssl.SslHandler;
import lombok.extern.slf4j.Slf4j;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import java.net.BindException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * @author fanlei
 * @version 1.0
 * @date 2019年01月25日
 */
@Slf4j
public class ProxyServerContainer implements Container, ProxyConfig.ConfigChangedListener{

    private static final int MAX_FRAME_LENGTH = 2*1024*1024;
    private static final int LENGTH_FIELD_OFFSET = 0;
    private static final int LENGTH_FIELD_LENGTH = 4;
    private static final int LENGTH_ADJUSTMENT = 0;
    private static final int INITIAL_BYTES_TO_STRIP = 0;

    private NioEventLoopGroup serverBossGroup = new NioEventLoopGroup();
    private NioEventLoopGroup serverWorkerGroup = new NioEventLoopGroup();

    public ProxyServerContainer() {
        ProxyConfig.getInstance().addConfigChangedListener(this);
    }

    @Override
    public void start() {
        ServerBootstrap bootstrap = new ServerBootstrap();
        ((ServerBootstrap)bootstrap.group(this.serverBossGroup, this.serverWorkerGroup).channel(NioServerSocketChannel.class)).childHandler(new ChannelInitializer<SocketChannel>() {
            public void initChannel(SocketChannel ch) throws Exception {
                /** ↓↓↓↓  解析自定义协议  ↓↓↓↓ **/
                ch.pipeline().addLast(new ChannelHandler[]{new ProxyMessageDecoder(MAX_FRAME_LENGTH, LENGTH_FIELD_OFFSET, LENGTH_FIELD_LENGTH, LENGTH_ADJUSTMENT, INITIAL_BYTES_TO_STRIP)});
                ch.pipeline().addLast(new ChannelHandler[]{new ProxyMessageEncoder()});
                /** ↑↑↑↑  解析自定义协议  ↑↑↑↑ **/

                /** Netty的IdleStateHandler心跳机制主要是用来检测远端是否存活，如果不存活或活跃则对空闲Socket连接进行处理避免资源的浪费 **/
                ch.pipeline().addLast(new ChannelHandler[]{new IdleCheckHandler(60, 40, 0)});

                ch.pipeline().addLast(new ChannelHandler[]{new ServerChannelHandler()});
            }
        });

        try {
            bootstrap.bind(ProxyConfig.getInstance().getServerBind(), ProxyConfig.getInstance().getServerPort()).get();
            log.info("proxy server start on port " + ProxyConfig.getInstance().getServerPort());
        } catch (Exception var4) {
            throw new RuntimeException(var4);
        }

        if (Config.getInstance().getBooleanValue("server.ssl.enable", false)) {
            String host = Config.getInstance().getStringValue("server.ssl.bind", "0.0.0.0");
            int port = Config.getInstance().getIntValue("server.ssl.port");
            this.initializeSSLTCPTransport(host, port, (new SslContextCreator()).initSSLContext());
        }

        this.startUserPort();
    }


    private void initializeSSLTCPTransport(String host, int port, final SSLContext sslContext) {
        ServerBootstrap b = new ServerBootstrap();
        ((ServerBootstrap)b.group(this.serverBossGroup, this.serverWorkerGroup).channel(NioServerSocketChannel.class)).childHandler(new ChannelInitializer<SocketChannel>() {
            public void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();

                try {
                    pipeline.addLast("ssl", ProxyServerContainer.this.createSslHandler(sslContext, Config.getInstance().getBooleanValue("server.ssl.needsClientAuth", false)));
                    ch.pipeline().addLast(new ChannelHandler[]{new ProxyMessageDecoder(2097152, 0, 4, 0, 0)});
                    ch.pipeline().addLast(new ChannelHandler[]{new ProxyMessageEncoder()});
                    ch.pipeline().addLast(new ChannelHandler[]{new IdleCheckHandler(60, 40, 0)});
                    ch.pipeline().addLast(new ChannelHandler[]{new ServerChannelHandler()});
                } catch (Throwable var4) {
                    ProxyServerContainer.log.error("Severe error during pipeline creation", var4);
                    throw var4;
                }
            }
        });

        try {
            ChannelFuture f = b.bind(host, port);
            f.sync();
            log.info("proxy ssl server start on port {}", port);
        } catch (InterruptedException var6) {
            log.error("An interruptedException was caught while initializing server", var6);
        }

    }

    private void startUserPort() {
        ServerBootstrap bootstrap = new ServerBootstrap();
        ((ServerBootstrap)bootstrap.group(this.serverBossGroup, this.serverWorkerGroup).channel(NioServerSocketChannel.class)).childHandler(new ChannelInitializer<SocketChannel>() {
            public void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addFirst(new ChannelHandler[]{new BytesMetricsHandler()});
                ch.pipeline().addLast(new ChannelHandler[]{new UserChannelHandler()});
            }
        });
        List<Integer> ports = ProxyConfig.getInstance().getUserPorts();
        Iterator i$ = ports.iterator();

        while(i$.hasNext()) {
            int port = (Integer)i$.next();

            try {
                bootstrap.bind(port).get();
                log.info("bind user port " + port);
            } catch (Exception var6) {
                if (!(var6.getCause() instanceof BindException)) {
                    throw new RuntimeException(var6);
                }
            }
        }

    }

    @Override
    public void onChanged() {
        this.startUserPort();
    }

    @Override
    public void stop() {
        this.serverBossGroup.shutdownGracefully();
        this.serverWorkerGroup.shutdownGracefully();
    }

    private ChannelHandler createSslHandler(SSLContext sslContext, boolean needsClientAuth) {
        SSLEngine sslEngine = sslContext.createSSLEngine();
        sslEngine.setUseClientMode(false);
        if (needsClientAuth) {
            sslEngine.setNeedClientAuth(true);
        }

        return new SslHandler(sslEngine);
    }

    public static void main(String[] args) {
        ContainerHelper.start(Arrays.asList(new ProxyServerContainer(), new WebConfigContainer()));
    }
}
